package sharetime.core.pager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


@SuppressWarnings("unchecked")
public abstract class AbstractPrefetchingPager implements Pager, Iterator {
	public static final Logger logger = LoggerFactory
			.getLogger(AbstractPrefetchingPager.class);

	protected int idx;

	private List prefetched;

	protected int indexOfFirstItemInCurrentPage;

	public boolean lastPage;

	private int preloadLimit;

	public AbstractPrefetchingPager() {
		idx = 0;
		prefetched = Collections.EMPTY_LIST;
		indexOfFirstItemInCurrentPage = 0;
		preloadLimit = 100;
	}

	public Iterator iterator() {
		return this;
	}

	public boolean isEmpty() {
		return !hasNext();
	}

	public List getCurrentPage() {
		return prefetched;
	}

	public boolean hasNext() {
		int indexWithinPage = getIndexWithinPage();
		if (indexWithinPage == prefetched.size() && !lastPage) {
			preload();
			indexWithinPage = getIndexWithinPage();
		}
		return indexWithinPage < prefetched.size() || !lastPage;
	}

	protected abstract void preload();

	public void remove() {
		throw new UnsupportedOperationException();
	}

	@SuppressWarnings("unchecked")
	protected void preload(Iterator iterator) {
		prefetched = new ArrayList();
		int currentPos = 0;
		try {
			do {
				if (lastPage || prefetched.size() >= preloadLimit)
					break;
				if (!iterator.hasNext()) {
					lastPage = true;
					break;
				}
				Object element = iterator.next();
				List entities = new ArrayList();
				fetch(element, entities);
				Iterator i = entities.iterator();
				while (i.hasNext() && prefetched.size() < preloadLimit) {
					Object r = i.next();
					if (currentPos >= idx)
						prefetched.add(r);
					currentPos++;
				}
			} while (true);
			if (prefetched.size() < preloadLimit)
				lastPage = true;
		} catch (Exception e) {
			logger.error("At index [" + idx + "]: " + e.getMessage(), e);
			lastPage = true;
		}
	}

	protected abstract List fetch(Object obj, List list) throws PagerException;

	public void nextPage() {
		idx += 100;
		preload();
	}

	public int getIndex() {
		return idx;
	}

	public void skipTo(int idx) throws PagerException {
		this.idx = idx;
		preload();
		if (prefetched == null || prefetched.size() == 0)
			this.idx = -1;
	}

	public boolean onLastPage() {
		return lastPage;
	}

	public int getIndexOfFirstItemInCurrentPage() {
		return indexOfFirstItemInCurrentPage;
	}

	public Object next() {
		if (!hasNext()) {
			throw new NoSuchElementException();
		} else {
			int indexWithinPage = getIndexWithinPage();
			Object nextObj = prefetched.get(indexWithinPage);
			idx++;
			return nextObj;
		}
	}

	protected int getIndexWithinPage() {
		return idx - indexOfFirstItemInCurrentPage;
	}
}
